﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using EasyFFmpeg.Controls;
using EasyFFmpeg.FFmpeg;
using EasyFFmpeg.Win32;
using System.Threading.Tasks;
using System.Threading;

namespace EasyFFmpeg
{
    public partial class FormMain : Form
    {
        private int processIndex = 0;
        private List<string> files = new List<string>();

        public FormMain()
        {
            InitializeComponent();
        }

        #region Event Handlers

        /// <summary>
        /// Called when the Add File button is clicked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnBtnAddFileClick(object sender, EventArgs e)
        {
            if (this.dlgFile.ShowDialog(this) == DialogResult.OK)
            {
                this.ToggleProcessingStatus(true);
                string[] fileNames = dlgFile.FileNames;
                foreach (string f in fileNames)
                {
                    this.AddFile(f);
                }

                this.ToggleProcessingStatus(false);
            }
        }
        /// <summary>
        /// Called when the Add Folder button is clicked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnBtnAddFolderClick(object sender, EventArgs e)
        {
#if DEBUG
            this.dlgFolder.SelectedPath = "D:\\Matt\\code\\EasyFFmpeg\\Test Files";
#endif
            if (this.dlgFolder.ShowDialog(this) == DialogResult.OK)
            {
                this.ToggleProcessingStatus(true);
                string folder = this.dlgFolder.SelectedPath;
                SearchOption searchOption = (chkRecursive.Checked ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);

                IEnumerable<string> fileNames = Directory.EnumerateFileSystemEntries(folder, "*.*", searchOption);
                foreach (string f in fileNames)
                {
                    if (File.Exists(f))
                    {
                        this.AddFile(f);
                    }
                }
                this.ToggleProcessingStatus(false);

            }

        }

        /// <summary>
        /// Called when the Convert button is clicked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnBtnBasicConvertClick(object sender, EventArgs e)
        {
            this.processIndex = 0;
            this.progressBar.Value = 0;
            this.progressBar.Minimum = 0;
            this.progressBar.Maximum = this.listFiles.Items.Count;

            this.ToggleProcessingStatus(true);
            this.ProcessNextFile();

        }

        /// <summary>
        /// Called when the Clear Files button is clicked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnBtnClearFilesClick(object sender, EventArgs e)
        {
            this.listFiles.Items.Clear();
            this.files.Clear();
        }


        /// <summary>
        /// Called when Remove button is clicked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnBtnRemoveClick(object sender, EventArgs e)
        {
            foreach (ListViewItem item in this.listFiles.SelectedItems)
            {
                FileInfo fi = (FileInfo) item.Tag;
                this.listFiles.Items.Remove(item);
                this.files.Remove(fi.FullName);
            }
        }

        /// <summary>
        /// Called when conversion of a file completes.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnConversionComplete(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new MethodInvoker(delegate() { OnConversionComplete(sender, e); }));
            }
            else
            {
                this.progressBar.Increment(1);
                this.processIndex += 1;
                if (processIndex < this.listFiles.Items.Count)
                {
                    this.ProcessNextFile();
                }
                else
                {
                    this.ToggleProcessingStatus(false);
                }
            }
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Form.Load"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            Version version = Assembly.GetEntryAssembly().GetName().Version;
            this.Text = String.Format("{0} {1}", this.Text, version.ToString(3));

            this.SetDefaultOptions();

        }


        /// <summary>
        /// Raised when additional output text is received.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnOutputReceived(object sender, DataReceivedEventArgs e)
        {
            AppendOutput(e.Data);
        }


        #endregion

        #region Private Methods

        /// <summary>
        /// Adds a file to the file list.
        /// </summary>
        /// <param name="fileName"></param>
        private void AddFile(string fileName)
        {
            if (this.files.Contains(fileName))
            {
                return;
            }

            this.files.Add(fileName);
            FileInfo fi = new FileInfo(fileName);

            ListViewItem item = new ListViewItem();
            item.Text = fi.Name;
            item.Tag = fi;
            item.Checked = true;

            StringBuilder sb = new StringBuilder(11);

            Win32Utils.StrFormatByteSize(fi.Length, sb, sb.Capacity);

            item.SubItems.Add(sb.ToString());
            item.SubItems.Add(fi.DirectoryName);

            this.listFiles.Items.Add(item);
        }

        /// <summary>
        /// Appends text to the output window.
        /// </summary>
        /// <param name="text"></param>
        private void AppendOutput(string text)
        {
            if (rtOutput.InvokeRequired)
            {
                rtOutput.BeginInvoke(new MethodInvoker(delegate() { AppendOutput(text); }));
            }
            else
            {
                if (text != null)
                {
                    // check for errors
                    string errorText = text.ToUpper();
                    bool errorOccurred = (
                        (errorText.IndexOf("ERROR WHILE OPENING CODEC") > -1) ||
                        (errorText.IndexOf("UNKNOWN FORMAT") > -1));

                    rtOutput.SelectionStart = rtOutput.TextLength;
                    rtOutput.SelectionColor = (errorOccurred ? Color.Red : Color.Black);
                    rtOutput.AppendText(text + Environment.NewLine);
                    rtOutput.SelectionStart = rtOutput.TextLength;
                    rtOutput.ScrollToCaret();

                    this.Refresh();
                    Application.DoEvents();

                }
            }
        }

        /// <summary>
        /// Gets the value of a ComboBox that contains ComboOptions as items.
        /// </summary>
        /// <param name="cb"></param>
        /// <returns></returns>
        private static string GetComboOptionValue(ComboBox cb)
        {
            ComboOption co = cb.SelectedItem as ComboOption;
            if (co != null)
            {
                return co.Value;
            }
            else
            {
                return cb.Text;
            }
        }

        /// <summary>
        /// Processes the next file in the list.
        /// </summary>
        private void ProcessNextFile()
        {
            string format = this.cbOutput.SelectedItem.ToString();
            ListViewItem lvi = this.listFiles.Items[this.processIndex];
            FileInfo fi = (FileInfo) lvi.Tag;

            // work out the output file name
            string sourceFile = fi.FullName;
            string outputFile = sourceFile.Replace(fi.Extension, format);
            string suffix = "_1";

            while (File.Exists(outputFile))
            {
                outputFile = sourceFile.Replace(fi.Extension, suffix + format);
                suffix = suffix + "_1";
            }
            this.AppendOutput("Processing " + sourceFile);

            // we now have an input and an output, run ffmpeg with the 
            // most basic options possible
            FFmpegWrapper ffmpegger = new FFmpegWrapper(sourceFile, outputFile);
            ffmpegger.AudioOptions.Channels = GetComboOptionValue(this.cbAudioChannels);

            ffmpegger.VideoOptions.Deinterlace = chkDeinterlace.Checked;

            ffmpegger.OutputReceived += new EventHandler<DataReceivedEventArgs>(OnOutputReceived);
            ffmpegger.ConversionCompleted += new EventHandler<EventArgs>(OnConversionComplete);
            ffmpegger.Convert();

        }

        /// <summary>
        /// Sets the default options on the controls.
        /// </summary>
        private void SetDefaultOptions()
        {
            // extensions
            cbOutput.SelectedItem = ".mkv";

            // audio channels
            cbAudioChannels.Items.Add(new ComboOption("", "Same as input"));
            cbAudioChannels.Items.Add(new ComboOption("1", "1 (Mono)"));
            cbAudioChannels.Items.Add(new ComboOption("2", "2 (Stereo)"));
            cbAudioChannels.Items.Add(new ComboOption("4", "4"));
            cbAudioChannels.Items.Add(new ComboOption("5", "5"));
            cbAudioChannels.Items.Add(new ComboOption("6", "6"));
            cbAudioChannels.SelectedIndex = 0;
        }

        /// <summary>
        /// Toggles the processing status of the form.
        /// </summary>
        /// <param name="isProcessing"></param>
        private void ToggleProcessingStatus(bool isProcessing)
        {
            if (isProcessing)
            {
                rtOutput.Clear();
            }

            this.progressBar.Visible = isProcessing;
            btnBasicConvert.Enabled = !isProcessing;
            Application.DoEvents();
        }

        #endregion


    }
}
